package org.loong.crypto.core.algorithm;

import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.asn1.iana.IANAObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.loong.crypto.core.Algorithm;
import org.loong.crypto.core.AlgorithmFamily;

/**
 * The HMAC algorithm.
 */
public final class HmacAlgorithm extends Algorithm {

    /**
     * The secret key length, zero if not specified.
     */
    private int keyLength;

    /**
     * HMAC using MD5 algorithm.
     */
    public static final HmacAlgorithm HMAC_MD5 = new HmacAlgorithm("HmacMD5", IANAObjectIdentifiers.hmacMD5, 128);

    /**
     * HMAC using SHA-1 algorithm.
     */
    public static final HmacAlgorithm HMAC_SHA1 = new HmacAlgorithm("HmacSHA1", IANAObjectIdentifiers.hmacSHA1, 128);

    /**
     * HMAC using SHA-224 algorithm.
     */
    public static final HmacAlgorithm HMAC_SHA224 = new HmacAlgorithm("HmacSHA224", PKCSObjectIdentifiers.id_hmacWithSHA224, 224);

    /**
     * HMAC using SHA-256 algorithm.
     */
    public static final HmacAlgorithm HMAC_SHA256 = new HmacAlgorithm("HmacSHA256", PKCSObjectIdentifiers.id_hmacWithSHA256, 256);

    /**
     * HMAC using SHA-384 algorithm.
     */
    public static final HmacAlgorithm HMAC_SHA384 = new HmacAlgorithm("HmacSHA384", PKCSObjectIdentifiers.id_hmacWithSHA384, 384);

    /**
     * HMAC using SHA-512 algorithm.
     */
    public static final HmacAlgorithm HMAC_SHA512 = new HmacAlgorithm("HmacSHA512", PKCSObjectIdentifiers.id_hmacWithSHA512, 512);

    /**
     * HMAC using SM3 algorithm.
     */
    public static final HmacAlgorithm HMAC_SM3 = new HmacAlgorithm("HmacSM3", GMObjectIdentifiers.hmac_sm3, 128);

    /**
     * The supported hmac algorithms.
     */
    public static final Map<String, HmacAlgorithm> SUPPORTED_ALGORITHMS;

    static {
        Map<String, HmacAlgorithm> algs = new LinkedHashMap<>();
        algs.put(HMAC_MD5.getName(), HMAC_MD5);
        algs.put(HMAC_SHA1.getName(), HMAC_SHA1);
        algs.put(HMAC_SHA224.getName(), HMAC_SHA224);
        algs.put(HMAC_SHA256.getName(), HMAC_SHA256);
        algs.put(HMAC_SHA384.getName(), HMAC_SHA384);
        algs.put(HMAC_SHA512.getName(), HMAC_SHA512);
        algs.put(HMAC_SM3.getName(), HMAC_SM3);
        SUPPORTED_ALGORITHMS = Collections.unmodifiableMap(algs);
    }

    /**
     * HMAC algorithm family.
     */
    public static final class Family extends AlgorithmFamily<HmacAlgorithm> {

        private static final long serialVersionUID = 1L;

        /**
         * HMAC using SHA/1/224/256/384/512 algorithms.
         */
        public static final Family HMAC_SHA = new Family(HMAC_SHA1, HMAC_SHA224, HMAC_SHA256, HMAC_SHA384, HMAC_SHA512);

        /**
         * Creates a new hmac algorithm family.
         *
         * @param algs The encryption algorithms of the family. Must not be {@code null}.
         */
        public Family(final HmacAlgorithm... algs) {
            super(algs);
        }
    }

    /**
     * Creates a new hmac algorithm name.
     * 
     * @param name the algorithm name. Must not be {@code null}.
     * @param oid the algorithm object identifier.
     */
    public HmacAlgorithm(final String name, final ASN1ObjectIdentifier oid) {
        this(name, oid, 0);
    }

    /**
     * Creates a new hmac algorithm name.
     * 
     * @param name the algorithm name. Must not be {@code null}.
     * @param oid the algorithm object identifier.
     * @param keyLength the secret key length, zero if not specified.
     */
    public HmacAlgorithm(final String name, final ASN1ObjectIdentifier oid, final int keyLength) {
        super(name, oid);

        this.keyLength = keyLength;
    }

    /**
     * Gets the value of keyLength.
     *
     * @return the value of keyLength
     */
    public int getKeyLength() {
        return keyLength;
    }

    /**
     * Parses a hmac algorithm from the specified string.
     *
     * @param name the algorithm name. Must not be {@code null}.
     * @return the hmac algorithm.
     * @throws NoSuchAlgorithmException the ${@link NoSuchAlgorithmException}
     */
    public static HmacAlgorithm find(final String name) throws NoSuchAlgorithmException {
        HmacAlgorithm algorithm = SUPPORTED_ALGORITHMS.get(name);
        if (algorithm == null) {
            throw new NoSuchAlgorithmException("Unknown hmac algorithm type requested: " + name);
        }

        return algorithm;
    }

    /**
     * Find a hmac algorithm from the specified string.
     *
     * @param algorithmId the hmac algorithm id. Must not be {@code null}.
     * @return the hmac algorithm.
     * @throws NoSuchAlgorithmException the ${@link NoSuchAlgorithmException}
     */
    public static HmacAlgorithm findByAlgorithmId(final AlgorithmIdentifier algorithmId) throws NoSuchAlgorithmException {
        final ASN1ObjectIdentifier oid = algorithmId.getAlgorithm();
        return SUPPORTED_ALGORITHMS.values().stream().filter(algorithm -> Objects.nonNull(algorithm.getOID()) && Objects.equals(algorithm.getOID(), oid)).findFirst()
                .orElseThrow(() -> new NoSuchAlgorithmException("Unknown hmac algorithm type requested: " + oid));
    }
}
